﻿// ------------------------------------------------------------------------------------
//      Copyright (c) uhavemyword@gmail.com. All rights reserved.
//      Created by Ben at 2/25/2013 2:14:55 PM
// ------------------------------------------------------------------------------------

namespace CP.General
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Data.Common;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Net;

    public static class ExceptionExtensions
    {
        /// <summary>
        /// Iterates the current exception stack and returns Sql Errors from all applicable Sql Exceptions
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <returns>An enumeration of the <see cref="SqlError"/> from all applicable <see cref="SqlException"/> instances </returns>
        public static IEnumerable<SqlError> AllSqlErrors(this Exception exception)
        {
            if (exception != null)
            {
                var sqlExceptions = exception.ExceptionStack().OfType<SqlException>();
                return sqlExceptions.SelectMany(e => e.Errors.Cast<SqlError>());
            }

            return Enumerable.Empty<SqlError>();
        }

        /// <summary>
        /// Checks if a given exception represents a Unique Constraint Violation with a given name
        /// </summary>
        /// <param name="exception">Exception instance</param>
        /// <param name="name">Constraint Name or part of it</param>
        /// <returns>True if unique constraint violation was detected. False otherwise</returns>
        public static bool IsUniqueConstraintViolation(this Exception exception, string name = null)
        {
            return exception
                    .AllSqlErrors()
                    .Any(e =>
                            e.Number == 2627 &&
                            (string.IsNullOrWhiteSpace(name) || e.Message.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0));
        }

        /// <summary>
        /// Checks if a given exception represents that entities may have been modified or deleted since entities were loaded.
        /// </summary>
        /// <param name="exception">Exception instance</param>
        /// <returns>True if concurrency exception was detected. False otherwise</returns>
        public static bool IsDbConcurrencyException(this Exception exception)
        {
            return exception.ExceptionStack().OfType<OptimisticConcurrencyException>().Count() > 0
                    || exception.ExceptionStack().OfType<DBConcurrencyException>().Count() > 0;
        }

        /// <summary>
        /// Generates flat list of inner exceptions associated with a given exception instance
        /// </summary>
        /// <param name="exception">Exception instance</param>
        /// <returns>Enumeration of inner exceptions</returns>
        public static IEnumerable<Exception> InnerExceptions(this Exception exception)
        {
            var next = exception.InnerException;
            while (next != null)
            {
                yield return next;
                next = next.InnerException;
            }

            yield break;
        }

        /// <summary>
        /// Iterates the current exception and all its inner exceptions.
        /// </summary>
        /// <param name="exception">The exception.</param>
        /// <returns>An enumeration of the exception and all its inner exceptions</returns>
        public static IEnumerable<Exception> ExceptionStack(this Exception exception)
        {
            yield return exception;

            foreach (var ex in exception.InnerExceptions())
            {
                yield return ex;
            }

            yield break;
        }

        public static Exception FindDatabaseException(this Exception exception)
        {
            return exception.ExceptionStack().FirstOrDefault(ex => ex is DbException || ex is DataException);
        }

        /// <summary>
        /// Gets response status from Web Exception
        /// </summary>
        /// <param name="exception">Original <see cref="WebException"/></param>
        /// <returns>Response Status Code</returns>
        public static int GetResponseStatus(this WebException exception)
        {
            if (exception != null && exception.Response != null)
            {
                var webResponse = exception.Response as HttpWebResponse;
                if (webResponse != null)
                {
                    return (int)webResponse.StatusCode;
                }
            }

            return -1;
        }
    }
}
